package com.potenza.edu.media.service;


import com.potenza.edu.media.config.RabbitMQConfig;
import com.potenza.edu.media.model.UploadChuckResult;
import org.apache.commons.codec.digest.DigestUtils;
import com.potenza.edu.media.utils.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class MediaUploadService {

    private final static Logger LOGGER = LoggerFactory.getLogger(MediaUploadService.class);
    //上传文件根目录
    @Value("${potenza-edu-media.upload-location}")
    String uploadPath;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    //文件上传注册
    public Result register(String fileMd5,
                           String fileName,
                           Long fileSize,
                           String mimetype,
                           String fileExt) {
        System.out.println("register " + fileMd5
                +" fileName<"+fileName +"> fileSize<"+fileSize+">"
                +" mimetype<"+mimetype +"> fileExt<"+fileExt+">");

        //检查文件是否上传
        //1、 得到文件的路径
        String filePath = getFilePath(fileMd5, fileExt);

        File file1 = new File(filePath);
        if(file1.exists()){//MD5 秒传
            return new Result(false,"1","该文件已经存在，不需要上传");//
        }
        createFileFold(fileMd5);
        createChunkFileFolder(fileMd5);
        /** 文件命名规则
         * 根据文件md5得到文件路径
         * 规则：
         * 一级目录：md5的第一个字符
         * 二级目录：md5的第二个字符
         * 三级目录：md5
         * 文件名：md5+文件扩展名
         */

        //2、 不存在创建目录
        //创建成功则返回
        //否则已经存在，不用上传
        return new Result(true,"200","创建目录成功");//
    }

    ////检查块文件
    //before-send
    public UploadChuckResult checkchunk(String fileMd5,
                                        Integer chunk,
                                        Integer chunkSize) {
        System.out.println("Thread:" + Thread.currentThread().getId() +"检查块是否存在 checkchunk " + fileMd5 +" chunk<"+chunk +"> chunkSize<"+chunkSize+">");
        //得到块文件所在路径
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);

        File chunkFile = new File(chunkFileFolderPath,chunk.toString());
        boolean chunkFileExist = chunkFile.exists();
        //块文件的文件名称以1,2,3..序号命名，没有扩展名
        if(chunkFileExist){
            System.out.println("Thread:" + Thread.currentThread().getId() +" chunk<"+chunk +">存在" );
            return new UploadChuckResult(true,"200","chunk<"+chunk+">存在",true);//
           }else{
            System.out.println("Thread:" + Thread.currentThread().getId() +" chunk<"+chunk +">不存在" );
            return new UploadChuckResult(true,"200","chunk<"+chunk+">不存在",false);//
        }
    }

    //upload
    public Result uploadchunk(MultipartFile file,
                                         String fileMd5,
                                         Integer chunk) {
        System.out.println("Thread:" + Thread.currentThread().getId() + " 上传文件 uploadchunk " + fileMd5 +" chunk<"+chunk +">");

        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);

        File chunkFile = new File(chunkFileFolderPath,chunk.toString());
        try {
            file.transferTo(chunkFile);
        } catch (IOException e) {
            throw new RuntimeException("文件块上传异常");
        }
        //上传该块
        //保存到相应的目录下
        return new Result(true,"200","上传块成功");
    }

    //after-send-file
    public Result mergechunks(String fileMd5,
                                      String fileName,
                                      Long fileSize,
                                      String mimetype,
                                      String fileExt) {
        System.out.println("合并文件 mergechunks " + fileMd5 +" fileName<"+fileName +">");
        //1、获取块文件的路径
        String filePath = getFilePath(fileMd5, fileExt);
        //2、合并文件路径
        //2.1创建合并文件
        File fileMerge = new File(filePath);//合并后的文件
        //2.2合并文件存在先删除再创建
        if(fileMerge.exists()){
            fileMerge.delete();
        }
        //3、合并文件
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);//c\5\c5c75d70f382e6016d2f506d134eee11\chunks\
        File chunkFileFolder = new File(chunkFileFolderPath);
        List<File> chunkFiles = getChunkFiles(chunkFileFolder);
        File fileMerged = mergeFile(fileMerge, chunkFiles);
        //4、MD5的校验，合并后的文件 计算MD5与源文件MD5比较
        //
        boolean success = checkFileMd5(fileMerged, fileMd5);

        //将记录保存到数据库中
        if(success){
            //备份到OSS上，采用断点续传
            Map<String,String> ossBackMap = new HashMap();
            ossBackMap.put("filePath",fileMerged.getAbsolutePath());
            ossBackMap.put("mimetype",mimetype);
            ossBackMap.put("fileMd5",fileMd5);
            ossBackMap.put("fileExt",fileExt);
            rabbitTemplate.convertAndSend("", RabbitMQConfig.QUEUE_NAME,ossBackMap);

            //5、异步调用-->RabbitMQ,向MQ发送视频处理消息
            return new Result(true,"200","合并文件成功:" + fileName);
        }else{
            return new Result(false,"500","合并文件出错:" + fileName);
        }
    }


    //校验文件的md5值
    private boolean checkFileMd5(File mergeFile,String fileMd5){
        if(mergeFile == null || StringUtils.isEmpty(fileMd5)){
            return false;
        }
        InputStream mergeFileInputstream = null;
        try {
            mergeFileInputstream = new FileInputStream(mergeFile);
            //得到文件的md5
            String mergeFileMd5 = DigestUtils.md5Hex(mergeFileInputstream);
            //比较md5
            if(fileMd5.equalsIgnoreCase(mergeFileMd5)){
                return true;
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("checkFileMd5 error,file is:{},md5 is:{}",mergeFile.getAbsoluteFile(),fileMd5);
        }finally {
            try {
                mergeFileInputstream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * @param mergeFile 合并前的初始文件
     * @param chunkFiles 需要合并的文件列表
     * @return 返回合并后的文件
     */
    private File mergeFile(File mergeFile,List<File> chunkFiles){
        try {
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");//read wirte
            //合并文件
            for(File chunkFile:chunkFiles){
                RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");//read
                int len = -1;
                byte[] b = new byte[1024];
                while((len=raf_read.read(b))!=-1){
                    raf_write.write(b,0,len);
                }
                raf_read.close();
            }
            raf_write.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return mergeFile;
    }

    /**
     * 获取目录下的文件列表
     * @param chunkfileFolder
     * @return
     */
    private  List<File>  getChunkFiles(File chunkfileFolder){
        File[] chunkFiles = chunkfileFolder.listFiles();
        List<File> chunkFileList = new ArrayList<File>(Arrays.asList(chunkFiles));

        chunkFileList = chunkFileList.stream().sorted((o1,o2)->{
            if(Integer.parseInt(o1.getName()) > Integer.parseInt(o2.getName())){
                return 1;
            }else{
                return -1;
            }
        }).collect(Collectors.toList());

        return chunkFileList;
    }


    /**
     * 创建 分片的文件 目录
     * @param fileMd5
     * @return
     */
    private boolean createChunkFileFolder(String fileMd5){
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        File chunkFileFolder = new File(chunkFileFolderPath);
        if(!chunkFileFolder.exists()){
            //创建文件夹
            boolean mkdirs = chunkFileFolder.mkdirs();
            return mkdirs;
        }
        return true;
    }
    /**
     * 根据文件md5得到文件路径
     * 规则：
     * 一级目录：md5的第一个字符
     * 二级目录：md5的第二个字符
     * 三级目录：md5
     * 文件名：md5+文件扩展名
     * @param fileMd5 文件md5值
     * @param fileExt 文件扩展名
     * @return 文件路径
     */
    private String getFilePath(String fileMd5,String fileExt){
        String filePath = uploadPath+fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) +
                "/" + fileMd5 + "/" + fileMd5 + "." + fileExt;
        return filePath;
    }
    //得到文件目录相对路径，路径中去掉根目录
    private String getFileFolderRelativePath(String fileMd5,String fileExt){
        String filePath = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" +
                fileMd5 + "/";
        return filePath;
    }

    /**
     * 获取文件的目录
     * @param fileMd5
     * @return
     */
    private String getFileFolderPath(String fileMd5){
        String fileFolder = uploadPath+fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) +
                "/" + fileMd5 + "/";
        return fileFolder;
    }

    /**
     * @param fileMd5
     * @return 得到块文件所在目录
     */
    private String getChunkFileFolderPath(String fileMd5){
        String fileChunkFolderPath = getFileFolderPath(fileMd5) +"/" + "chunks" + "/";
        return fileChunkFolderPath;
    }

    /**
     * 创建文件路径
     * @param fileMd5
     * @return
     */
    private boolean createFileFold(String fileMd5){
        String fileFolderPath = getFileFolderPath(fileMd5);
        File fileFolder = new File(fileFolderPath);
        if(!fileFolder.exists()){
            //创建文件夹
            boolean mkdirs = fileFolder.mkdirs();
            return mkdirs;
        }
        return true;
    }
}
